/*
Copyright 2025 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package helm

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
	"sigs.k8s.io/kubebuilder/v4/test/e2e/utils"
)

const (
	tokenRequestRawString = `{"apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest"}`
)

// tokenRequest is a trimmed down version of the authentication.k8s.io/v1/TokenRequest Type
// that we want to use for extracting the token.
type tokenRequest struct {
	Status struct {
		Token string `json:"token"`
	} `json:"status"`
}

var _ = Describe("kubebuilder", func() {
	Context("plugin helm/v2-alpha", func() {
		var kbc *utils.TestContext

		BeforeEach(func() {
			var err error
			kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on")
			Expect(err).NotTo(HaveOccurred())
			Expect(kbc.Prepare()).To(Succeed())
		})

		AfterEach(func() {
			By("removing restricted namespace label")
			_ = kbc.RemoveNamespaceLabelToEnforceRestricted()

			By("uninstalling Helm Release (if installed)")
			_ = kbc.UninstallHelmRelease()

			By("removing controller image and working dir")
			kbc.Destroy()
		})

		It("should generate a runnable project using webhooks and installed with the HelmChart", func() {
			generateHelmProject(kbc)
			By("installing Helm")
			Expect(kbc.InstallHelm()).To(Succeed())

			runHelm(kbc, true, true, false)
		})

		It("should generate a runnable project without metrics exposed", func() {
			generateHelmProjectWithoutMetrics(kbc)
			By("installing Helm")
			Expect(kbc.InstallHelm()).To(Succeed())

			runHelm(kbc, true, false, false)
		})

		It("should generate a runnable project with metrics protected by network policies", func() {
			generateHelmProjectWithNetworkPoliciesWithoutWebhooks(kbc)
			By("installing Helm")
			Expect(kbc.InstallHelm()).To(Succeed())

			runHelm(kbc, false, true, true)
		})

		It("should generate a runnable project with webhooks and metrics protected by network policies", func() {
			generateHelmProjectWithNetworkPolicies(kbc)
			By("installing Helm")
			Expect(kbc.InstallHelm()).To(Succeed())

			runHelm(kbc, true, true, true)
		})

		It("should generate a runnable project with the manager running as restricted and without webhooks", func() {
			generateHelmProjectWithoutWebhooks(kbc)
			By("installing Helm")
			Expect(kbc.InstallHelm()).To(Succeed())

			runHelm(kbc, false, true, false)
		})
	})
})

// runHelm executes the helm-based deployment and validations similar to go/v4 Run,
// but always installs via the Helm chart generated by the helm/v2-alpha plugin.
func runHelm(kbc *utils.TestContext, hasWebhook, hasMetrics, hasNetworkPolicies bool) {
	var controllerPodName string
	var err error

	By("creating manager namespace")
	err = kbc.CreateManagerNamespace()
	Expect(err).NotTo(HaveOccurred())

	By("labeling the namespace to enforce the restricted security policy")
	err = kbc.LabelNamespacesToEnforceRestricted()
	Expect(err).NotTo(HaveOccurred())

	By("updating the go.mod")
	err = kbc.Tidy()
	Expect(err).NotTo(HaveOccurred())

	By("run make all")
	err = kbc.Make("all")
	Expect(err).NotTo(HaveOccurred())

	By("building the controller image")
	err = kbc.Make("docker-build", "IMG="+kbc.ImageName)
	Expect(err).NotTo(HaveOccurred())

	By("loading the controller docker image into the kind cluster")
	err = kbc.LoadImageToKindCluster()
	Expect(err).NotTo(HaveOccurred())

	By("building the installer manifest for helm chart generation")
	err = kbc.Make("build-installer", "IMG="+kbc.ImageName)
	Expect(err).NotTo(HaveOccurred(), "Failed to build installer manifest")

	By("building the helm-chart")
	err = kbc.EditHelmPlugin()
	Expect(err).NotTo(HaveOccurred(), "Failed to edit project to generate helm-chart")

	By("updating values with image name")
	values := filepath.Join(kbc.Dir, "dist", "chart", "values.yaml")
	err = pluginutil.ReplaceInFile(values, "repository: controller", "repository: e2e-test/controller-manager")
	Expect(err).NotTo(HaveOccurred(), "Failed to edit repository in the chart/values.yaml")
	err = pluginutil.ReplaceInFile(values, "tag: latest", fmt.Sprintf("tag: %s", kbc.TestSuffix))
	Expect(err).NotTo(HaveOccurred(), "Failed to edit tag in the chart/values.yaml")

	By("updating values to enable prometheus")
	err = pluginutil.ReplaceInFile(values, "prometheus:\n  enable: false", "prometheus:\n  enable: true")
	Expect(err).NotTo(HaveOccurred(), "Failed to enable prometheus in the chart/values.yaml")

	if hasWebhook {
		By("updating values to enable cert-manager and cleanup CRDs on uninstall")
		// ensure CRDs are not kept on uninstall for cleanup in tests
		err = pluginutil.ReplaceInFile(values, "keep: true", "keep: false")
		Expect(err).NotTo(HaveOccurred(), "Failed to set keep false in the chart/values.yaml")
	}

	By("install with Helm release")
	err = kbc.HelmInstallRelease()
	Expect(err).NotTo(HaveOccurred(), "Failed to install helm release")

	By("Checking controllerManager and getting the name of the Pod")
	controllerPodName = getControllerName(kbc)

	By("Checking if all flags are applied to the manager pod")
	podOutput, err := kbc.Kubectl.Get(
		true,
		"pod", controllerPodName,
		"-o", "jsonpath={.spec.containers[0].args}",
	)
	Expect(err).NotTo(HaveOccurred())
	Expect(podOutput).To(ContainSubstring("leader-elect"),
		"Expected manager pod to have --leader-elect flag")
	Expect(podOutput).To(ContainSubstring("health-probe-bind-address"),
		"Expected manager pod to have --health-probe-bind-address flag")

	By("validating that the Prometheus manager has provisioned the Service")
	Eventually(func(g Gomega) {
		_, err = kbc.Kubectl.Get(
			false,
			"Service", "prometheus-operator")
		g.Expect(err).NotTo(HaveOccurred())
	}, time.Minute, time.Second).Should(Succeed())

	By("validating that the ServiceMonitor for Prometheus is applied in the namespace")
	_, err = kbc.Kubectl.Get(
		true,
		"ServiceMonitor")
	Expect(err).NotTo(HaveOccurred())

	if hasNetworkPolicies {
		if hasMetrics {
			By("labeling the namespace to allow consume the metrics")
			Expect(kbc.Kubectl.Command("label", "namespaces", kbc.Kubectl.Namespace,
				"metrics=enabled")).Error().NotTo(HaveOccurred())

			By("Ensuring the Allow Metrics Traffic NetworkPolicy exists", func() {
				var output string
				output, err = kbc.Kubectl.Get(
					true,
					"networkpolicy", fmt.Sprintf("e2e-%s-allow-metrics-traffic", kbc.TestSuffix),
				)
				Expect(err).NotTo(HaveOccurred(), "NetworkPolicy allow-metrics-traffic should exist in the namespace")
				Expect(output).To(
					ContainSubstring("allow-metrics-traffic"),
					"NetworkPolicy allow-metrics-traffic should be present in the output",
				)
			})
		}

		if hasWebhook {
			By("labeling the namespace to allow webhooks traffic")
			_, err = kbc.Kubectl.Command("label", "namespaces", kbc.Kubectl.Namespace,
				"webhook=enabled")
			Expect(err).NotTo(HaveOccurred())

			By("Ensuring the allow-webhook-traffic NetworkPolicy exists", func() {
				var output string
				output, err = kbc.Kubectl.Get(
					true,
					"networkpolicy", fmt.Sprintf("e2e-%s-allow-webhook-traffic", kbc.TestSuffix),
				)
				Expect(err).NotTo(HaveOccurred(), "NetworkPolicy allow-webhook-traffic should exist in the namespace")
				Expect(output).To(
					ContainSubstring("allow-webhook-traffic"),
					"NetworkPolicy allow-webhook-traffic should be present in the output",
				)
			})
		}
	}

	if hasWebhook {
		By("validating that cert-manager has provisioned the certificate Secret")

		verifyWebhookCert := func(g Gomega) {
			var output string
			output, err = kbc.Kubectl.Get(
				true,
				"secrets", "webhook-server-cert")
			g.Expect(err).ToNot(HaveOccurred(), "webhook-server-cert should exist in the namespace")
			g.Expect(output).To(ContainSubstring("webhook-server-cert"))
		}

		Eventually(verifyWebhookCert, time.Minute, time.Second).Should(Succeed())

		By("validating that the mutating|validating webhooks have the CA injected")
		verifyCAInjection := func(g Gomega) {
			var mwhOutput, vwhOutput string
			mwhOutput, err = kbc.Kubectl.Get(
				false,
				"mutatingwebhookconfigurations.admissionregistration.k8s.io",
				fmt.Sprintf("e2e-%s-mutating-webhook-configuration", kbc.TestSuffix),
				"-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
			g.Expect(err).NotTo(HaveOccurred())
			// check that ca should be long enough, because there may be a place holder "\n"
			g.Expect(len(mwhOutput)).To(BeNumerically(">", 10))

			vwhOutput, err = kbc.Kubectl.Get(
				false,
				"validatingwebhookconfigurations.admissionregistration.k8s.io",
				fmt.Sprintf("e2e-%s-validating-webhook-configuration", kbc.TestSuffix),
				"-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
			g.Expect(err).NotTo(HaveOccurred())
			// check that ca should be long enough, because there may be a place holder "\n"
			g.Expect(len(vwhOutput)).To(BeNumerically(">", 10))
		}

		Eventually(verifyCAInjection, time.Minute, time.Second).Should(Succeed())
	}

	By("creating an instance of the CR")
	sampleFile := filepath.Join("config", "samples",
		fmt.Sprintf("%s_%s_%s.yaml", kbc.Group, kbc.Version, strings.ToLower(kbc.Kind)))
	sampleFilePath, err := filepath.Abs(filepath.Join(fmt.Sprintf("e2e-%s", kbc.TestSuffix), sampleFile))
	Expect(err).To(Not(HaveOccurred()))

	f, err := os.OpenFile(sampleFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
	Expect(err).To(Not(HaveOccurred()))
	defer func() {
		err = f.Close()
		Expect(err).To(Not(HaveOccurred()))
	}()
	_, err = f.WriteString("  foo: bar")
	Expect(err).To(Not(HaveOccurred()))

	applySample := func(g Gomega) {
		_, applyErr := kbc.Kubectl.Apply(true, "-f", sampleFile)
		g.Expect(applyErr).NotTo(HaveOccurred())
	}
	Eventually(applySample, time.Minute, time.Second).Should(Succeed())

	By("validating that the controller-manager pod is running as expected")
	verifyControllerUp := func(g Gomega) error {
		// Get pod name
		const podListGoTemplate = "go-template={{ range .items }}" +
			"{{ if not .metadata.deletionTimestamp }}" +
			"{{ .metadata.name }}" +
			"{{ \"\\n\" }}" +
			"{{ end }}" +
			"{{ end }}"
		podOutput, getErr := kbc.Kubectl.Get(
			true,
			"pods", "-l", "control-plane=controller-manager",
			"-o", podListGoTemplate)
		g.Expect(getErr).NotTo(HaveOccurred())
		podNames := pluginutil.GetNonEmptyLines(podOutput)
		if len(podNames) != 1 {
			return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
		}
		controllerPodName = podNames[0]
		g.Expect(controllerPodName).Should(ContainSubstring("controller-manager"))

		// Validate pod status
		status, statusErr := kbc.Kubectl.Get(
			true,
			"pods", controllerPodName, "-o", "jsonpath={.status.phase}")
		g.Expect(statusErr).NotTo(HaveOccurred())
		if status != "Running" {
			return fmt.Errorf("controller pod in %s status", status)
		}
		return nil
	}
	Eventually(verifyControllerUp, 5*time.Minute, time.Second).Should(Succeed())

	if hasMetrics {
		_ = getMetricsOutput(kbc)
	} else {
		metricsShouldBeUnavailable(kbc)
	}

	if hasWebhook {
		By("validating that the CRD conversion was properly configured and works as expected")

		// Ensure CRD conversion webhook has CA injected before testing conversion
		By("waiting for CRD conversion CA bundle to be injected")
		crdName := fmt.Sprintf("conversiontests.%s.%s", kbc.Group, kbc.Domain)
		Eventually(func(g Gomega) {
			out, errGet := kbc.Kubectl.Get(
				true,
				"crd", crdName,
				"-o", "jsonpath={.spec.conversion.webhook.clientConfig.caBundle}",
			)
			g.Expect(errGet).NotTo(HaveOccurred())
			g.Expect(len(strings.TrimSpace(out))).To(BeNumerically(">", 10))
		}, 2*time.Minute, 2*time.Second).Should(Succeed())

		// Update the ConversionTest CR sample in v1 to set a specific `size`
		By("modifying the ConversionTest CR sample to set `size` for conversion testing")
		conversionCRFile := filepath.Join("config", "samples",
			fmt.Sprintf("%s_v1_conversiontest.yaml", kbc.Group))
		conversionCRPath := filepath.Join(kbc.Dir, conversionCRFile)

		// Edit the file to include `size` in the spec field for v1
		err = pluginutil.ReplaceInFile(conversionCRPath, "# TODO(user): Add fields here", `size: 3`)
		Expect(err).NotTo(HaveOccurred(), "failed to replace spec in ConversionTest CR sample")

		// Apply the ConversionTest Custom Resource in v1
		By("applying the modified ConversionTest CR in v1 for conversion")
		_, err = kbc.Kubectl.Apply(true, "-f", conversionCRPath)
		Expect(err).NotTo(HaveOccurred(), "failed to apply modified ConversionTest CR")

		By("waiting for the ConversionTest CR to appear")
		Eventually(func(g Gomega) {
			_, err := kbc.Kubectl.Get(true, "conversiontest", "conversiontest-sample")
			g.Expect(err).NotTo(HaveOccurred(), "expected the ConversionTest CR to exist")
		}, time.Minute, time.Second).Should(Succeed())

		By("validating that the converted resource in v2 has replicas == 3")
		Eventually(func(g Gomega) {
			out, err := kbc.Kubectl.Get(
				true,
				"conversiontest", "conversiontest-sample",
				"-o", "jsonpath={.spec.replicas}",
			)
			g.Expect(err).NotTo(HaveOccurred(), "failed to get converted resource in v2")
			replicas, err := strconv.Atoi(out)
			g.Expect(err).NotTo(HaveOccurred(), "replicas field is not an integer")
			g.Expect(replicas).To(Equal(3), "expected replicas to be 3 after conversion")
		}, 2*time.Minute, 2*time.Second).Should(Succeed())
	}
}

func getControllerName(kbc *utils.TestContext) string {
	By("validating that the controller-manager pod is running as expected")
	var controllerPodName string
	verifyControllerUp := func(g Gomega) error {
		// Get pod name
		const podListGoTemplate = "go-template={{ range .items }}" +
			"{{ if not .metadata.deletionTimestamp }}" +
			"{{ .metadata.name }}" +
			"{{ \"\\n\" }}" +
			"{{ end }}" +
			"{{ end }}"
		podOutput, err := kbc.Kubectl.Get(
			true,
			"pods", "-l", "control-plane=controller-manager",
			"-o", podListGoTemplate)
		g.Expect(err).NotTo(HaveOccurred())
		podNames := pluginutil.GetNonEmptyLines(podOutput)
		if len(podNames) != 1 {
			return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
		}
		controllerPodName = podNames[0]
		g.Expect(controllerPodName).Should(ContainSubstring("controller-manager"))

		// Validate pod status
		status, err := kbc.Kubectl.Get(
			true,
			"pods", controllerPodName, "-o", "jsonpath={.status.phase}")
		g.Expect(err).NotTo(HaveOccurred())
		if status != "Running" {
			return fmt.Errorf("controller pod in %s status", status)
		}
		return nil
	}
	defer func() {
		out, err := kbc.Kubectl.CommandInNamespace("describe", "all")
		Expect(err).NotTo(HaveOccurred())
		_, _ = fmt.Fprintln(GinkgoWriter, out)
	}()
	Eventually(verifyControllerUp, 5*time.Minute, time.Second).Should(Succeed())
	return controllerPodName
}

// getMetricsOutput return the metrics output from curl pod
func getMetricsOutput(kbc *utils.TestContext) string {
	_, err := kbc.Kubectl.Command(
		"get", "clusterrolebinding", fmt.Sprintf("metrics-%s", kbc.TestSuffix),
	)
	if err != nil && strings.Contains(err.Error(), "NotFound") {
		// Create the clusterrolebinding only if it doesn't exist
		_, err = kbc.Kubectl.Command(
			"create", "clusterrolebinding", fmt.Sprintf("metrics-%s", kbc.TestSuffix),
			fmt.Sprintf("--clusterrole=e2e-%s-metrics-reader", kbc.TestSuffix),
			fmt.Sprintf("--serviceaccount=%s:%s", kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount),
		)
		Expect(err).NotTo(HaveOccurred())
	} else {
		Expect(err).NotTo(HaveOccurred(), "Failed to check clusterrolebinding existence")
	}

	token, err := serviceAccountToken(kbc)
	Expect(err).NotTo(HaveOccurred())
	Expect(token).NotTo(BeEmpty())

	var metricsOutput string
	By("validating that the controller-manager service is available")
	_, err = kbc.Kubectl.Get(
		true,
		"service", fmt.Sprintf("e2e-%s-controller-manager-metrics-service", kbc.TestSuffix),
	)
	Expect(err).NotTo(HaveOccurred(), "Controller-manager service should exist")

	By("ensuring the service endpoint is ready")
	checkServiceEndpoint := func(g Gomega) {
		var output string
		output, err = kbc.Kubectl.Get(
			true,
			"endpoints", fmt.Sprintf("e2e-%s-controller-manager-metrics-service", kbc.TestSuffix),
			"-o", "jsonpath={.subsets[*].addresses[*].ip}",
		)
		g.Expect(err).NotTo(HaveOccurred(), "endpoints should exist")
		g.Expect(output).ShouldNot(BeEmpty(), "no endpoints found")
	}
	Eventually(checkServiceEndpoint, 2*time.Minute, time.Second).Should(Succeed(),
		"Service endpoint should be ready")

	// See comment in v4 suite about metrics readiness delay on newer k8s
	By("waiting briefly to ensure that the certs are provisioned and metrics are available")
	time.Sleep(15 * time.Second)

	By("creating a curl pod to access the metrics endpoint")
	cmdOpts := cmdOptsToCreateCurlPod(kbc, token)
	_, err = kbc.Kubectl.CommandInNamespace(cmdOpts...)
	Expect(err).NotTo(HaveOccurred())

	By("validating that the controllerManager ServiceAccount exists")
	saName := kbc.Kubectl.ServiceAccount
	currentSAOutput, err := kbc.Kubectl.Get(
		true,
		"serviceaccount", saName,
		"-o", "jsonpath={.metadata.name}",
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to fetch the service account")
	Expect(currentSAOutput).To(Equal(saName), "The ServiceAccount in use does not match the expected one")

	By("validating that the metrics endpoint is serving as expected")
	getCurlLogs := func(g Gomega) {
		metricsOutput, err = kbc.Kubectl.Logs("curl")
		g.Expect(err).NotTo(HaveOccurred())
		g.Expect(metricsOutput).Should(ContainSubstring("< HTTP/1.1 200 OK"))
	}
	Eventually(getCurlLogs, 10*time.Second, time.Second).Should(Succeed())
	removeCurlPod(kbc)
	return metricsOutput
}

func metricsShouldBeUnavailable(kbc *utils.TestContext) {
	_, err := kbc.Kubectl.Command(
		"create", "clusterrolebinding", fmt.Sprintf("metrics-%s", kbc.TestSuffix),
		fmt.Sprintf("--clusterrole=e2e-%s-metrics-reader", kbc.TestSuffix),
		fmt.Sprintf("--serviceaccount=%s:%s", kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount))
	Expect(err).NotTo(HaveOccurred())

	token, err := serviceAccountToken(kbc)
	Expect(err).NotTo(HaveOccurred())
	Expect(token).NotTo(BeEmpty())

	By("creating a curl pod to access the metrics endpoint")
	cmdOpts := cmdOptsToCreateCurlPod(kbc, token)
	_, err = kbc.Kubectl.CommandInNamespace(cmdOpts...)
	Expect(err).NotTo(HaveOccurred())

	By("validating that the curl pod fail as expected")
	verifyCurlUp := func(g Gomega) {
		status, errCurl := kbc.Kubectl.Get(
			true,
			"pods", "curl", "-o", "jsonpath={.status.phase}")
		g.Expect(errCurl).NotTo(HaveOccurred())
		g.Expect(status).NotTo(Equal("Failed"),
			fmt.Sprintf("curl pod in %s status when should fail with an error", status))
	}
	Eventually(verifyCurlUp, 240*time.Second, time.Second).Should(Succeed())

	By("validating that the metrics endpoint is not working as expected")
	getCurlLogs := func(g Gomega) {
		metricsOutput, err := kbc.Kubectl.Logs("curl")
		g.Expect(err).NotTo(HaveOccurred())
		g.Expect(metricsOutput).Should(ContainSubstring("Could not resolve host"))
	}
	Eventually(getCurlLogs, 10*time.Second, time.Second).Should(Succeed())
	removeCurlPod(kbc)
}

func cmdOptsToCreateCurlPod(kbc *utils.TestContext, token string) []string {
	//nolint:lll
	cmdOpts := []string{
		"run", "curl",
		"--restart=Never",
		"--namespace", kbc.Kubectl.Namespace,
		"--image=curlimages/curl:latest",
		"--overrides",
		fmt.Sprintf(`{
            "spec": {
                "containers": [{
                    "name": "curl",
                    "image": "curlimages/curl:latest",
                    "command": ["/bin/sh", "-c"],
                    "args": ["curl -v -k -H 'Authorization: Bearer %s' https://e2e-%s-controller-manager-metrics-service.%s.svc.cluster.local:8443/metrics"],
                    "securityContext": {
                        "readOnlyRootFilesystem": true,
                        "allowPrivilegeEscalation": false,
                        "capabilities": {
                            "drop": ["ALL"]
                        },
                        "runAsNonRoot": true,
                        "runAsUser": 1000,
                        "seccompProfile": {
                            "type": "RuntimeDefault"
                        }
                    }
                }],
                "serviceAccountName": "%s"
            }
    }`, token, kbc.TestSuffix, kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount),
	}
	return cmdOpts
}

func removeCurlPod(kbc *utils.TestContext) {
	By("cleaning up the curl pod")
	_, err := kbc.Kubectl.Delete(true, "pods/curl", "--grace-period=0", "--force")
	Expect(err).NotTo(HaveOccurred())
}

// serviceAccountToken provides a helper function that can provide you with a service account
// token that you can use to interact with the service. This function leverages the k8s'
// TokenRequest API in raw format in order to make it generic for all version of the k8s that
// is currently being supported in kubebuilder test infra.
// TokenRequest API returns the token in raw JWT format itself. There is no conversion required.
func serviceAccountToken(kbc *utils.TestContext) (string, error) {
	var out string

	secretName := fmt.Sprintf("%s-token-request", kbc.Kubectl.ServiceAccount)
	tokenRequestFile := filepath.Join(kbc.Dir, secretName)
	if err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o755)); err != nil {
		return out, fmt.Errorf("error creating token request file %s: %w", tokenRequestFile, err)
	}
	getToken := func(g Gomega) {
		// Output of this is already a valid JWT token. No need to covert this from base64 to string format
		rawJSON, err := kbc.Kubectl.Command(
			"create",
			"--raw", fmt.Sprintf(
				"/api/v1/namespaces/%s/serviceaccounts/%s/token",
				kbc.Kubectl.Namespace,
				kbc.Kubectl.ServiceAccount,
			),
			"-f", tokenRequestFile,
		)

		g.Expect(err).NotTo(HaveOccurred())
		var token tokenRequest
		err = json.Unmarshal([]byte(rawJSON), &token)
		g.Expect(err).NotTo(HaveOccurred())

		out = token.Status.Token
	}
	Eventually(getToken, time.Minute, time.Second).Should(Succeed())

	return out, nil
}

// generateHelmProject sets up a go/v4 project with webhooks and conversion webhook enabled,
// and prepares kustomize configs so the helm plugin can generate full charts.
func generateHelmProject(kbc *utils.TestContext) {
	initHelmProject(kbc)
	createAPI(kbc)

	By("scaffolding mutating and validating webhooks")
	err := kbc.CreateWebhook(
		"--group", kbc.Group,
		"--version", kbc.Version,
		"--kind", kbc.Kind,
		"--defaulting",
		"--programmatic-validation",
		"--make=false",
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to scaffolding mutating webhook")

	By("implementing the mutating and validating webhooks")
	webhookFilePath := filepath.Join(
		kbc.Dir, "internal/webhook", kbc.Version,
		fmt.Sprintf("%s_webhook.go", strings.ToLower(kbc.Kind)))
	err = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))
	Expect(err).NotTo(HaveOccurred(), "Failed to implement webhooks")

	scaffoldConversionWebhook(kbc)

	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../prometheus", "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "prometheus", "kustomization.yaml"),
		monitorTLSPatch, "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsCertPatch, "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsCertReplaces, "#")).To(Succeed())
}

func generateHelmProjectWithoutMetrics(kbc *utils.TestContext) {
	initHelmProject(kbc)
	createAPI(kbc)

	By("scaffolding mutating and validating webhooks")
	err := kbc.CreateWebhook(
		"--group", kbc.Group,
		"--version", kbc.Version,
		"--kind", kbc.Kind,
		"--defaulting",
		"--programmatic-validation",
		"--make=false",
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to scaffolding mutating webhook")

	By("implementing the mutating and validating webhooks")
	webhookFilePath := filepath.Join(
		kbc.Dir, "internal/webhook", kbc.Version,
		fmt.Sprintf("%s_webhook.go", strings.ToLower(kbc.Kind)))
	err = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))
	Expect(err).NotTo(HaveOccurred(), "Failed to implement webhooks")

	scaffoldConversionWebhook(kbc)
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../prometheus", "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.CommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"- metrics_service.yaml", "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.CommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsTarget, "#")).To(Succeed())
}

func generateHelmProjectWithNetworkPoliciesWithoutWebhooks(kbc *utils.TestContext) {
	initHelmProject(kbc)
	createAPI(kbc)

	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../prometheus", "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsTarget, "#")).To(Succeed())
	By("uncomment kustomization.yaml to enable network policy")
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../network-policy", "#")).To(Succeed())
}

func generateHelmProjectWithNetworkPolicies(kbc *utils.TestContext) {
	initHelmProject(kbc)
	createAPI(kbc)

	By("scaffolding mutating and validating webhooks")
	err := kbc.CreateWebhook(
		"--group", kbc.Group,
		"--version", kbc.Version,
		"--kind", kbc.Kind,
		"--defaulting",
		"--programmatic-validation",
		"--make=false",
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to scaffolding mutating webhook")

	By("implementing the mutating and validating webhooks")
	webhookFilePath := filepath.Join(
		kbc.Dir, "internal/webhook", kbc.Version,
		fmt.Sprintf("%s_webhook.go", strings.ToLower(kbc.Kind)))
	err = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))
	Expect(err).NotTo(HaveOccurred(), "Failed to implement webhooks")

	scaffoldConversionWebhook(kbc)
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../prometheus", "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsTarget, "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsCertPatch, "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		metricsCertReplaces, "#")).To(Succeed())
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "prometheus", "kustomization.yaml"),
		monitorTLSPatch, "#")).To(Succeed())

	By("uncomment kustomization.yaml to enable network policy")
	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../network-policy", "#")).To(Succeed())
}

func generateHelmProjectWithoutWebhooks(kbc *utils.TestContext) {
	initHelmProject(kbc)
	createAPI(kbc)

	ExpectWithOffset(1, pluginutil.UncommentCode(
		filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"),
		"#- ../prometheus", "#")).To(Succeed())
}

func createAPI(kbc *utils.TestContext) {
	By("creating API definition")
	err := kbc.CreateAPI(
		"--group", kbc.Group,
		"--version", kbc.Version,
		"--kind", kbc.Kind,
		"--namespaced",
		"--resource",
		"--controller",
		"--make=false",
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to create API")

	By("implementing the API")
	ExpectWithOffset(1, pluginutil.InsertCode(
		filepath.Join(kbc.Dir, "api", kbc.Version, fmt.Sprintf("%s_types.go", strings.ToLower(kbc.Kind))),
		fmt.Sprintf(`type %sSpec struct {
`, kbc.Kind),
		"\t// +optional\nCount int `json:\"count,omitempty\"`\n")).Should(Succeed())
}

func initHelmProject(kbc *utils.TestContext) {
	By("initializing a project")
	err := kbc.Init(
		"--plugins", "go/v4",
		"--project-version", "3",
		"--domain", kbc.Domain,
	)
	Expect(err).NotTo(HaveOccurred(), "Failed to initialize project")
}

const metricsTarget = `- path: manager_metrics_patch.yaml
  target:
    kind: Deployment`

// scaffoldConversionWebhook sets up conversion webhooks for testing the ConversionTest API
func scaffoldConversionWebhook(kbc *utils.TestContext) {
	By("scaffolding conversion webhooks for testing ConversionTest v1 to v2 conversion")

	// Create API for v1 (hub) with conversion enabled
	err := kbc.CreateAPI(
		"--group", kbc.Group,
		"--version", "v1",
		"--kind", "ConversionTest",
		"--controller=true",
		"--resource=true",
		"--make=false",
	)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create v1 API for conversion testing")

	// Create API for v2 (spoke) without a controller
	err = kbc.CreateAPI(
		"--group", kbc.Group,
		"--version", "v2",
		"--kind", "ConversionTest",
		"--controller=false",
		"--resource=true",
		"--make=false",
	)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create v2 API for conversion testing")

	// Create the conversion webhook for v1
	By("setting up the conversion webhook for v1")
	err = kbc.CreateWebhook(
		"--group", kbc.Group,
		"--version", "v1",
		"--kind", "ConversionTest",
		"--conversion",
		"--spoke", "v2",
		"--make=false",
	)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create conversion webhook for v1")

	// Insert Size field in v1
	By("implementing the size spec in v1")
	ExpectWithOffset(1, pluginutil.InsertCode(
		filepath.Join(kbc.Dir, "api", "v1", "conversiontest_types.go"),
		"Foo *string `json:\"foo,omitempty\"`",
		"\n\tSize int `json:\"size,omitempty\"` // Number of desired instances",
	)).NotTo(HaveOccurred(), "failed to add size spec to conversiontest_types v1")

	// Insert Replicas field in v2
	By("implementing the replicas spec in v2")
	ExpectWithOffset(1, pluginutil.InsertCode(
		filepath.Join(kbc.Dir, "api", "v2", "conversiontest_types.go"),
		"Foo *string `json:\"foo,omitempty\"`",
		"\n\tReplicas int `json:\"replicas,omitempty\"` // Number of replicas",
	)).NotTo(HaveOccurred(), "failed to add replicas spec to conversiontest_conversion.go v2")

	err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"),
		"// dst.Spec.Size = src.Spec.Replicas",
		"dst.Spec.Size = src.Spec.Replicas")
	Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v1 to v2")

	err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"),
		"// dst.Spec.Replicas = src.Spec.Size",
		"dst.Spec.Replicas = src.Spec.Size")
	Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v2 to v1")
}

const monitorTLSPatch = `#patches:
#  - path: monitor_tls_patch.yaml
#    target:
#      kind: ServiceMonitor`

const metricsCertPatch = `#- path: cert_metrics_manager_patch.yaml
#  target:
#    kind: Deployment`

const metricsCertReplaces = `# - source: # Uncomment the following block to enable certificates for metrics
#     kind: Service
#     version: v1
#     name: controller-manager-metrics-service
#     fieldPath: metadata.name
#   targets:
#     - select:
#         kind: Certificate
#         group: cert-manager.io
#         version: v1
#         name: metrics-certs
#       fieldPaths:
#         - spec.dnsNames.0
#         - spec.dnsNames.1
#       options:
#         delimiter: '.'
#         index: 0
#         create: true
#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor
#         kind: ServiceMonitor
#         group: monitoring.coreos.com
#         version: v1
#         name: controller-manager-metrics-monitor
#       fieldPaths:
#         - spec.endpoints.0.tlsConfig.serverName
#       options:
#         delimiter: '.'
#         index: 0
#         create: true

# - source:
#     kind: Service
#     version: v1
#     name: controller-manager-metrics-service
#     fieldPath: metadata.namespace
#   targets:
#     - select:
#         kind: Certificate
#         group: cert-manager.io
#         version: v1
#         name: metrics-certs
#       fieldPaths:
#         - spec.dnsNames.0
#         - spec.dnsNames.1
#       options:
#         delimiter: '.'
#         index: 1
#         create: true
#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor
#         kind: ServiceMonitor
#         group: monitoring.coreos.com
#         version: v1
#         name: controller-manager-metrics-monitor
#       fieldPaths:
#         - spec.endpoints.0.tlsConfig.serverName
#       options:
#         delimiter: '.'
#         index: 1
#         create: true`
